use super::error::FrameResult;
use super::fhd::*;
use super::wd::*;

use crate::byte::*;
use crate::block::Block;
use crate::workspace::OutputWorkSpace;
use crate::workspace::WorkSpace;
pub const ZSTD_FRAME_MAGIC:u32 = 0xFD2FB528;

#[derive(Debug)]
pub struct ZstdFrame {
    header:FrameHeader,
    blocks:Vec<Block>,
    content_checksum:Option<u32>,
}

impl ZstdFrame {
    pub fn serialize_into<W:ByteWrite>(&self,writer:&mut ByteWriter<W>) -> FrameResult<()> {
        self.header.serialize_into(writer)?;
        for b in &self.blocks {
            b.serialize_into(writer)?;
        }
        if self.header.descriptor.contain_content_checksum() {
            writer.write_from_u32(self.content_checksum.unwrap())?;
        }
        Ok(())
    }

    pub fn deserialize_from<R:ByteRead>(reader:&mut ByteReader<R>, workspace:&mut WorkSpace) -> FrameResult<Self> {
        let header = FrameHeader::deserialize_from(reader)?;
        let mut blocks = Vec::new();
        loop {
            let block = Block::deserialize_from(reader, workspace)?;
            let break_flag = block.header().is_last_block();
            blocks.push(block);
            if break_flag { break; }
        }
        let content_checksum = match header.descriptor.contain_content_checksum() {
            true => Some(reader.read_to_u32()?),
            false => None,
        };
        Ok(Self {
            header,
            blocks,
            content_checksum,
        })
    }

    pub fn uncompressed_to<O:OutputStream>(&self, output:&mut O, workspce:&mut OutputWorkSpace) -> OutputResult<usize>  {
        let mut total_len = 0;
        for b in &self.blocks {
            total_len += b.uncompressed_to(output, workspce)?;
        }
        Ok(total_len)
    }
}

#[derive(Debug)]
pub struct FrameHeader {
    descriptor:FrameHeaderDescriptor,
    window_descriptor:Option<WindowDescriptor>,
    dictionary_id:Option<u32>,
    frame_content_size:Option<u64>,
}

impl FrameHeader {
    pub fn serialize_into<W:ByteWrite>(&self,writer:&mut ByteWriter<W>) -> FrameResult<()> {
        writer.write_from_u8(self.descriptor.clone().into())?;
        match &self.window_descriptor {
            Some(wd) => { writer.write_from_u8(wd.clone().into())?; }
            None => { }
        }
        match self.descriptor.did_field_size() {
            DidFieldSize::None => None,
            DidFieldSize::U8 => Some(writer.write_from_u8(self.dictionary_id.unwrap() as u8)?),
            DidFieldSize::U16 => Some(writer.write_from_u16(self.dictionary_id.unwrap() as u16)?),
            DidFieldSize::U32 => Some(writer.write_from_u32(self.dictionary_id.unwrap())?),
        };
        match self.descriptor.fcs_field_size() {
            FcsFieldSize::None => None,
            FcsFieldSize::U8 => Some(writer.write_from_u8(self.frame_content_size.unwrap() as u8)?),
            FcsFieldSize::U16 => Some(writer.write_from_u16(self.frame_content_size.unwrap() as u16)?),
            FcsFieldSize::U32 => Some(writer.write_from_u32(self.frame_content_size.unwrap() as u32)?),
            FcsFieldSize::U64 => Some(writer.write_from_u64(self.frame_content_size.unwrap())?),
        };
        Ok(())
    }

    pub fn deserialize_from<R:ByteRead>(reader:&mut ByteReader<R>) -> FrameResult<Self> {
        let descriptor = FrameHeaderDescriptor::from(reader.read_to_u8()?);
        let window_descriptor = match descriptor.contain_window_descriptor() {
            true => Some(WindowDescriptor::from(reader.read_to_u8()?)),
            false => None,
        };
        let dictionary_id = match descriptor.did_field_size() {
            DidFieldSize::None => None,
            DidFieldSize::U8 => Some(reader.read_to_u8()? as u32),
            DidFieldSize::U16 => Some(reader.read_to_u16()? as u32),
            DidFieldSize::U32 => Some(reader.read_to_u32()?),
        };
        let frame_content_size = match descriptor.fcs_field_size() {
            FcsFieldSize::None => None,
            FcsFieldSize::U8 => Some(reader.read_to_u8()? as u64),
            FcsFieldSize::U16 => Some(reader.read_to_u16()? as u64),
            FcsFieldSize::U32 => Some(reader.read_to_u32()? as u64),
            FcsFieldSize::U64 => Some(reader.read_to_u64()?),
        };
        Ok(Self {
            descriptor,
            window_descriptor,
            dictionary_id,
            frame_content_size,
        })
    }
}